#ifndef _NE_VECTOR4_H_
#define _NE_VECTOR4_H_

#include <mathematics/Vector3.h>

namespace ne
{
    class NEAPI Vector4
    {
        public:
            inline Vector4()
                : x(0.0f), y(0.0f), z(0.0f), w(0.0f)
            {
            }

            inline explicit Vector4(const real v)
                : x(v), y(v), z(v), w(v)
            {
            }

            inline Vector4(const Vector4 &v)
                : x(v.x), y(v.y), z(v.z), w(v.w)
            {
            }

            inline explicit Vector4(const real* const v)
                : x(v[0]), y(v[1]), z(v[2]), w(v[3])
            {
            }

            inline explicit Vector4(const Vector2 &v)
                : x(v.x), y(v.y), z(0.0f), w(1.0f)
            {
            }

            inline explicit Vector4(const Vector3 &v)
                : x(v.x), y(v.y), z(v.z), w(1.0f)
            {
            }

            inline Vector4(const real vx, const real vy, const real vz, const real vw)
                : x(vx), y(vy), z(vz), w(vw)
            {
            }

            inline void swap(Vector4 &v)
            {
                std::swap(x, v.x);
                std::swap(y, v.y);
                std::swap(z, v.z);
                std::swap(w, v.w);
            }

            inline real operator [] (const size_t v) const
            {
                assert(v < 4);
                return *(&x + v);
            }

            inline real& operator [] (const size_t v)
            {
                assert(v < 4);
                return *(&x + v);
            }

            inline real* ptr()
            {
                return &x;
            }

            inline const real* ptr() const
            {
                return &x;
            }

            // Evaluate
            inline Vector4& operator = (const real v)
            {
                x = v;
                y = v;
                z = v;
                w = v;
                return *this;
            }

            inline Vector4& operator = (const Vector4 &v)
            {
                x = v.x;
                y = v.y;
                z = v.z;
                w = v.w;
                return *this;
            }

            // Equal
            inline bool operator == (const Vector4 &v) const
            {
                return (x == v.x) && (y == v.y) && (z == v.z) && (w == v.w);
            }

            // Inequivalence
            inline bool operator != (const Vector4 &v) const
            {
                return (x != v.x) || (y != v.y) || (z != v.z) || (w != v.w);
            }

            inline bool operator < (const Vector4 &v) const
            {
                return (x < v.x) && (y < v.y) && (z < v.z) && (w < v.w);
            }

            inline bool operator > (const Vector4 &v) const
            {
                return (x > v.x) && (y > v.y) && (z > v.z) && (w > v.w);
            }

            // Addition
            inline Vector4 operator + (const real v) const
            {
                return Vector4(x + v, y + v, z + v, w + v);
            }

            inline Vector4 operator + (const Vector4 &v) const
            {
                return Vector4(x + v.x, y + v.y, z + v.z, w + v.w);
            }

            NEAPI friend Vector4 operator + (const real vl, const Vector4 &vr);

            // Subtration
            inline Vector4 operator - (const real v) const
            {
                return Vector4(x - v, y - v, z - v, w - v);
            }

            inline Vector4 operator - (const Vector4 &v) const
            {
                return Vector4(x - v.x, y - v.y, z - v.z, w - v.w);
            }

            NEAPI friend Vector4 operator - (const real vl, const Vector4 & vr);

            // Multiplication
            inline Vector4 operator * (const real v) const
            {
                return Vector4(x * v, y * v, z * v, w * v);
            }

            inline Vector4 operator * (const Vector4 &v) const
            {
                return Vector4(x * v.x, y * v.y, z * v.z, w * v.w);
            }

            NEAPI friend Vector4 operator * (const real vl, const Vector4 &vr);

            // Division
            inline Vector4 operator / (const real v) const
            {
                assert(v != 0.0f);
                return Vector4(x / v, y / v, z / v, w / v);
            }

            inline Vector4 operator / (const Vector4 &v) const
            {
                assert((v.x != 0.0f) && (v.y != 0.0f) && (v.z != 0.0f) && (v.w != 0.0f));
                return Vector4(x / v.x, y / v.y, z / v.z, w / v.w);
            }

            NEAPI friend Vector4 operator / (const real vl, const Vector4 &vr);

            // Self updates
            inline Vector4& operator += (const real v)
            {
                x += v;
                y += v;
                z += v;
                w += v;
                return *this;
            }

            inline Vector4& operator += (const Vector4 &v)
            {
                x += v.x;
                y += v.y;
                z += v.z;
                w += v.w;
                return *this;
            }

            inline Vector4& operator -= (const real v)
            {
                x -= v;
                y -= v;
                z -= v;
                w -= v;
                return *this;
            }

            inline Vector4& operator -= (const Vector4 &v)
            {
                x -= v.x;
                y -= v.y;
                z -= v.z;
                w -= v.w;
                return *this;
            }

            inline Vector4& operator *= (const real v)
            {
                x *= v;
                y *= v;
                z *= v;
                w *= v;
                return *this;
            }

            inline Vector4& operator *= (const Vector4 &v)
            {
                x *= v.x;
                y *= v.y;
                z *= v.z;
                w *= v.w;
                return *this;
            }

            inline Vector4& operator /= (const real v)
            {
                assert(v != 0.0f);
                x /= v;
                y /= v;
                z /= v;
                w /= v;
                return *this;
            }

            inline Vector4& operator /= (const Vector4 &v)
            {
                assert((v.x != 0.0f) && (v.y != 0.0f) && (v.z != 0.0f) && (v.w != 0.0f));
                x /= v.x;
                y /= v.y;
                z /= v.z;
                w /= v.w;
                return *this;
            }

            // Signed
            inline const Vector4& operator + () const
            {
                return *this;
            }

            inline Vector4 operator - () const
            {
                return Vector4(-x, -y, -z, -w);
            }

            inline real dotProduct(const Vector4 &v) const
            {
                return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w);
            }

            inline void makeFloor(const Vector4 &v)
            {
                if (x > v.x) x = v.x;
                if (y > v.y) y = v.y;
                if (z > v.z) z = v.z;
                if (w > v.w) w = v.w;
            }

            inline void makeCeil(const Vector4 &v)
            {
                if (x < v.x) x = v.x;
                if (y < v.y) y = v.y;
                if (z < v.z) z = v.z;
                if (w < v.w) w = v.w;
            }

            // Function for writing to a stream.
            NEAPI friend std::ostream& operator << (std::ostream &o, const Vector4 &v);

            inline ~Vector4()
            {
            }

        public:
            static bool Equal(const Vector4 &vS, const Vector4 &vD, const real tolerance=Math::EPSILON);

            static Vector4 Mid(const Vector4 &vS, const Vector4 &vD);

            static Vector4 Lerp(const Vector4 &vS, const Vector4 &vD, const real u);

            static Vector4 Bezier(const Vector4 &vS, const Vector4 &vA, const Vector4 &vB, const Vector4 &vD, const real u);

        public:
            static const Vector4 ZERO;
            static const Vector4 ONE;
            static const Vector4 UNIT_X;
            static const Vector4 UNIT_Y;
            static const Vector4 UNIT_Z;
            static const Vector4 UNIT_W;
            static const Vector4 NEGATIVE_ONE;
            static const Vector4 NEGATIVE_UNIT_X;
            static const Vector4 NEGATIVE_UNIT_Y;
            static const Vector4 NEGATIVE_UNIT_Z;
            static const Vector4 NEGATIVE_UNIT_W;

        public:
            real x, y, z, w;
    };
}
#endif
